import xml.dom.minidom;
import os;
import os.path;
import re;
import datetime;
import copy;
from xml.dom.minidom import parse, parseString;
from string import Template;
import cStringIO;

DEFAULT_LANG = 'x-default';
CLOSED_CHOICE = 'closed';

schemas = {};
typedefs = {};
properties = {};
types = {}

def parseXmpFile(file):
    dom = parse(file)
    schema = Schema(dom.documentElement.getAttribute('name'), dom.documentElement.getAttribute('uri'), dom.documentElement.getAttribute('prefix'))
    for typedefElement in dom.getElementsByTagName('typedef'):
        name = typedefElement.getAttribute('name');
        typedef = Typedef(name, parseType(findFirstChildElement(typedefElement),name))
        typedefs[typedef.name] = typedef
    
    for propertyElement in dom.getElementsByTagName('property'):
        name = propertyElement.getAttribute('name');
        property = Property(schema, name, parseType(findFirstChildElement(propertyElement),schema.prefix+":"+name))
        parseDescriptionsAndLabels(property, propertyElement)
        properties[property.getPrefixedName()] = property;

    schemas[schema.prefix] = schema;
    
def findFirstChildElement(element):
    for child in element.childNodes:
        if child.nodeType == xml.dom.minidom.Node.ELEMENT_NODE:
            return child;
    return None;

def getTextChild(element):
    for child in element.childNodes:
        if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
            return re.sub('\s+', ' ', child.nodeValue);
    return None;

def getSchema(prefix):
    if schemas.has_key(prefix):
        return schemas[prefix];
    return None;

def getProperty(property,prefix = None):
    if isinstance(property,Property):
        return property;
    if prefix == None:
        if properties.has_key(property):
            return properties[property];
    elif schemas.has_key(prefix):
        return schemas[prefix].getProperty(property);
    return None;

def getType(path):
    return types.get(path);
    
def __init__():
    parseXmpFile(os.path.join(os.path.dirname(__file__),'xmp_builtins.xml'));
    folder = os.path.join(os.path.dirname(__file__),'xmp');
    for root, dirs, files in os.walk(folder):
        for file in files:
            if file.endswith('.xml'):
               parseXmpFile(os.path.join(root, file))

def parseType(element,path):
    choiceType = element.getAttribute('choice');
    type = None;
    if element.tagName == 'type':
        name = element.getAttribute('name');
        if name == 'Integer':
            type = Integer(path,choiceType);
        elif name == 'Text':
            type = Text(path,choiceType);
        elif name == 'Real':
            type = Real(path,choiceType);
        elif name == 'Date':
            type = Date(path,choiceType);
        elif name == 'Boolean':
            type = Boolean(path,choiceType);
        elif typedefs.has_key(name):
            type = typedefs.get(name).newType(path);
        else:
            print 'Type not found: '+name;
        parseChoices(element,type);
    elif element.tagName == 'structure':
        type = Structure(element.getAttribute('uri'), element.getAttribute('prefix'),path,choiceType);
        for fieldElement in element.getElementsByTagName('field'):
            name = fieldElement.getAttribute('name');
            type.addField(Field(name, parseType(findFirstChildElement(fieldElement),path+'.'+name)));
        parseChoices(element,type);
    elif element.tagName == 'bag':
        type = parseChoices(element,Bag(parseType(findFirstChildElement(element),path+'#'),choiceType));
    elif element.tagName == 'sequence':
        type = parseChoices(element,Sequence(parseType(findFirstChildElement(element),path+'#'),choiceType));
    elif element.tagName == 'alternative':
        type = parseChoices(element,Alternative(parseType(findFirstChildElement(element),path+'#'),choiceType));
    if type != None:
        types[type.path] = type;
    return type;
    
def parseChoices(element,type):
    if type != None:
        for choiceElement in element.getElementsByTagName('choice'):
            if choiceElement.parentNode == element:
                choice = Choice(type.parse(choiceElement.getAttribute('value')));
                parseDescriptionsAndLabels(choice,choiceElement);
                type.addChoice(choice);
    return type;

    
def parseDescriptionsAndLabels(object,element):
    for descriptionElement in element.getElementsByTagName('description'):
        object.setDescription(descriptionElement.getAttribute('xml:lang'),getTextChild(descriptionElement));
    for labelElement in element.getElementsByTagName('label'):
        object.setLabel(labelElement.getAttribute('xml:lang'),getTextChild(labelElement));
        
def splitPath(path):
    if len(path) == 0:
        return None;
    else:
        return (path[0],path[1:]);
        
class LabelsDescriptions(object):
    
    def __init__(self):
        self.labels = {};
        self.descriptions = {};
        self.languages = set();
    
    def setLabel(self,lang,label):
        self.languages.add(lang);
        self.labels[lang] = label;
        
    def getLabel(self,lang):
        if self.labels.has_key(lang):
            return self.labels[lang];
        return None;

    def setDescription(self,lang,description):
        self.languages.add(lang);
        self.descriptions[lang] = description;
        
    def getDescription(self,lang):
        if self.descriptions.has_key(lang):
            return self.descriptions[lang];
        return None;
    
class Schema(object):
    
    def __init__(self,name,uri,prefix):
        self.name = name;
        self.uri = uri;
        self.prefix = prefix;
        self.properties = {};
        
    def addProperty(self,property):
        self.properties[property.name] = property;
        
    def getProperty(self,name):
        return self.properties.get(name);
    
class Typedef(object):
    
    def __init__(self,name,type):
        self.name = name;
        self.typedecl = type;
        
    def newType(self,path):
        type = self.typedecl.copy(self.typedecl.path,path,self.name);
        type.path = path;
        return type;
        
class Property(LabelsDescriptions):
    
    def __init__(self,schema,name,type):
        LabelsDescriptions.__init__(self);
        self.name = name;
        self.typedecl = type;
        self.schema = schema;
        schema.addProperty(self);
    
    def getLabelOrName(self,lang):
        if self.labels.has_key(lang):
            return self.labels[lang];
        return self.name;
    
    def getPrefixedName(self):
        return self.schema.prefix+":"+self.name;
    
    def format(self,value,lang = DEFAULT_LANG):
        return self.typedecl.format(value,lang);
    
    def parse(self,text):
        return self.typedecl.parse(text);
    
    def getValue(self,complexValue,path):
        return self.typedecl.getValue(complexValue,path);
    
    def setValue(self,complexValue,path,value):
        return self.typedecl.setValue(complexValue,path,value);
    
    def getType(self,path):
        return self.typedecl.getType(path);
    
    def walkTypes(self,callback):
        self.typedecl.walk(callback);
    
class Type(object):
    
    def __init__(self,path,choiceType,name):
        self.choices = {};
        self.choiceType = choiceType;
        self.path = path;
        self.name = name;
        
    def isArray(self):
        return False;
    
    def isSequence(self):
        return False;
    
    def isBag(self):
        return False;
    
    def isAlternative(self):
        return False;
    
    def isStructure(self):
        return False;
    
    def isArray(self):
        return False;
    
    def isSimple(self):
        return False;
    
    def addChoice(self,choice):
        self.choices[choice.value] = choice;
        
    def getChoice(self,value):
        return self.choices[value];
    
    def parse(self,text):
        return text;
    
    def format(self,value,lang = DEFAULT_LANG):
        if value == None:
            return '';
        return str(value);
    
    def getChoices(self):
        return self.choices;
    
    def getValue(self,value,path):
        return value;
    
    def setValue(self,complexValue,path,value):
        return value;
    
    def getType(self,path):
        return self;
    
    def walk(self,callback):
        callback(self);
        
    def copy(self,oldPath,newPath,name):
        newType = copy.copy(self);
        newType.path = newPath+self.path[len(oldPath):];
        newType.name = name;
        return newType;
    
class SimpleType(Type):
    
    def __init__(self,path,choiceType,name):
        super(SimpleType,self).__init__(path,choiceType,name);
        
    def isSimple(self):
        return True;
    
class Real(SimpleType):
    
    def __init__(self,path,choiceType):
        super(Real,self).__init__(path,choiceType,'Real');
        self.min = None;
        self.max = None;
        
    def parse(self,text):
        if len(text) == 0:
            return None;
        return float(text);
    
    def format(self,value,lang = DEFAULT_LANG):
        if value == None:
            return '';
        return str(value);

class Integer(SimpleType):
    
    def __init__(self,path,choiceType):
        super(Integer,self).__init__(path,choiceType,'Integer');
        self.min = None;
        self.max = None;
        
    def parse(self,text):
        if len(text) == 0:
            return None;
        return int(text);
    
    def format(self,value,lang = DEFAULT_LANG):
        if value == None:
            return '';
        return str(value);
    
#dateParser = re.compile('^(?P<year>\d\d\d\d)(?:-(?P<month>\d\d)(?:-(?P<day>\d\d)(?:T(?P<hour>\d\d):(?P<minute>\d\d)(?::(?P<second>\d\d))?(?:(?P<Z>Z)|(?P<tzs>[+-])(?P<tzh>\d\d):(?P<tzm>\d\d)))?)?)?$');
dateParser = re.compile('^(?P<year>\d\d\d\d)(?::(?P<month>\d\d)(?::(?P<day>\d\d)(?: (?P<hour>\d\d):(?P<minute>\d\d)(?::(?P<second>\d\d))?)?)?)?$');

def intOrNone(text):
    if text == None:
        return None;
    return int(text);

class Date(SimpleType):
    
    def __init__(self,path,choiceType):
        super(Date,self).__init__(path,choiceType,'Date');
        
    def parse(self,text):
        if len(text) == 0:
            return None;
        matcher = dateParser.match(text);        

        if matcher == None:
            return None;
        year = intOrNone(matcher.group('year'));
        month = intOrNone(matcher.group('month'));
        day = intOrNone(matcher.group('day'));
        hour = intOrNone(matcher.group('hour'));
        minute = intOrNone(matcher.group('minute'));
        second = intOrNone(matcher.group('second'));
#        timeZoneZ = matcher.group('Z') != None;
#        timeZoneSign = matcher.group('tzs');
#        timeZoneHour = intOrNone(matcher.group('tzh'));
#        timeZoneMinute = intOrNone(matcher.group('tzm'));
        
        return (year,month,day,hour,minute,second);
        
    def format(self,value,lang = DEFAULT_LANG):
        if value == None:
            return '';
        year,month,day,hour,minute,second = value;
        if hour != None:
            if second != None:
                return '%04d-%02d-%02dT%02d:%02d:%02d+00:00' % value[0:6];
            else:
                return '%04d-%02d-%02dT%02d:%02d+00:00' % value[0:5];
        elif day != None:
            return '%04d-%02d-%02d' % value[0:3];
        elif month != None:
            return '%04d-%02d' % value[0:2];
        else:
            return '%04d' % value[0:1];
    
class Text(SimpleType):
    
    def __init__(self,path,choiceType):
        super(Text,self).__init__(path,choiceType,'Text');
    
class Boolean(SimpleType):
    
    def __init__(self,path,choiceType):
        super(Boolean,self).__init__(path,choiceType,'Boolean');
        
    def parse(self,text):
        if text == 'True':
            return True;
        elif text == 'False':
            return False;
        return None;
    
    def format(self,value,lang = DEFAULT_LANG):
        if value == None:
            return '';
        elif value == True:
            return 'True';
        else:
            return 'False';

class Array(Type):
    
    def __init__(self,elementsType,path,choiceType,name):
        super(Array,self).__init__(path,choiceType,name);
        self.elementsType = elementsType;
        
    def isArray(self):
        return True;
    
    def getType(self,path):
        key,path = self.splitPath(path);
        return self.elementsType.getType(path);

    def walk(self,callback):
        Type.walk(self, callback);
        self.elementsType.walk(callback);
        
    def copy(self,oldPath,newPath,name):
        newType = Type.copy(self, oldPath, newPath,name);
        newType.elementsType = self.elementsType.copy(oldPath,newPath,self.elementsType.name);
        return newType;
            
class Alternative(Array):
    
    def __init__(self,elementsType,path,choiceType = None):
        super(Alternative,self).__init__(elementsType,path,choiceType,'Alternative');
    
    def isAlternative(self):
        return True;
    
    def format(self,value,lang = DEFAULT_LANG):
        if len(value) == 0:
            return '';
        if value.has_key('default'):
            return self.elementsType.format(value['default']);
        lang,v = value.iteritems().next();
        return self.elementsType.format(v);
    
    def getValue(self,value,path):
        key,path = self.splitPath(path);
        if value.hasKey(key):
            return self.elementsType.getValue(value[key],path);
        else:
            return None;
    
    def setValue(self,complexValue,path,value):
        key,path = self.splitPath(path);
        if complexValue == None:
            complexValue = {key: self.elementsType.setValue(None,path,value)};
        else:
            if complexValue.hasKey(key):
                oldValue = complexValue[key];
            else:
                oldValue = None;
            complexValue[key] = self.elementsType.setValue(oldValue,path,value);
        return complexValue;
    
class Bag(Array):
    
    def __init__(self,elementsType,path,choiceType = None):
        super(Bag,self).__init__(elementsType,path,choiceType,'Bag');
    
    def isBag(self):
        return True;
    
    def format(self,value,lang = DEFAULT_LANG):
        text = cStringIO.StringIO();
        try:
            text.write('( ');
            first = True;
            for v in value:
                if not first:
                    text.write(',');
                first = False;
                text.write(self.elementsType.format(v,lang));
            text.write(' )');
            return text.getvalue();
        finally:
            text.close();
    
    def getValue(self,value,path):
        index,path = self.splitPath(path);
        return self.elementsType.getValue(value[index],path);
    
    def setValue(self,complexValue,path,value):
        index,path = self.splitPath(path);
        if complexValue == None:
            complexValue = [ ];
        else:
            for i in range(len(complexValue)-1,index):
                complexValue.append(None);
            complexValue[index] = self.elementsType.setValue(complexValue[index],path,value);
        return complexValue;
    
class Sequence(Array):
    
    def __init__(self,elementsType,path,choiceType = None):
        super(Sequence,self).__init__(elementsType,path,choiceType,'Sequence');
    
    def isSequence(self):
        return True;
    
    def format(self,value,lang = DEFAULT_LANG):
        text = cStringIO.StringIO();
        try:
            text.write('( ');
            first = True;
            for v in value:
                if not first:
                    text.write(',');
                first = False;
                text.write(self.elementsType.format(v,lang));
            text.write(' )');
            return text.getvalue();
        finally:
            text.close();
    
    def getValue(self,value,path):
        index,path = self.splitPath(path);
        return self.elementsType.getValue(value[index],path);
    
    def setValue(self,complexValue,path,value):
        index,path = self.splitPath(path);
        if complexValue == None:
            complexValue = [ ];
        else:
            for i in range(len(complexValue)-1,index):
                complexValue.append(None);
            complexValue[index] = self.elementsType.setValue(complexValue[index],path,value);
        return complexValue;
    
class Choice(LabelsDescriptions):
    
    def __init__(self,value):
        LabelsDescriptions.__init__(self);
        self.value = value;
        
class Field(LabelsDescriptions):
    
    def __init__(self,name,type):
        LabelsDescriptions.__init__(self);
        self.name = name;
        self.typedecl = type;
        
    def copy(self,oldPath,newPath):
        newField = copy.copy(self);
        newField.typedecl = self.typedecl.copy(oldPath,newPath,self.typedecl.name);
        return newField;
    
class Structure(Type):
    
    def __init__(self,uri,prefix,path,choiceType):
        super(Structure,self).__init__(path,choiceType,'Structure');
        self.uri = uri;
        self.prefix = prefix;
        self.fields = {}
        
    def addField(self,field):
        self.fields[field.name] = field;
        
    def getField(self,name):
        return self.fields[name];
    
    def isStructure(self):
        return True;
    
    def format(self,value,lang = DEFAULT_LANG):
        text = cStringIO.StringIO();
        try:
            first = True;
            for name,field in self.fields.iteritems():
                if not first:
                    text.write(',');
                if value.has_key(name):
                    text.write(name);
                    text.write('=');
                    text.write(field.typedecl.format(value[name],lang));
                    first = False;
            return text.getvalue();
        finally:
            text.close();
    
    def getValue(self,value,path):
        field,path = self.splitPath(path);
        if value.hasKey(field):
            return self.elementsType.getValue(value[field],path);
        else:
            return None;
    
    def setValue(self,complexValue,path,value):
        field,path = self.splitPath(path);
        if complexValue == None:
            complexValue = {field: self.elementsType.setValue(None,path,value)};
        else:
            if complexValue.hasKey(field):
                oldValue = complexValue[field];
            else:
                oldValue = None;
            complexValue[field] = self.elementsType.setValue(oldValue,path,value);
        return complexValue;
    
    def getType(self,path):
        fieldName,path = self.splitPath(path);
        field = self.getField(fieldName);
        if field == None:
            return None;
        else:
            return field.typedecl.getType(path);
        
    def walk(self,callback):
        Type.walk(self, callback);
        for field in self.fields.itervalues():
            field.typedecl.walk(callback);
        
    def copy(self,oldPath,newPath,name):
        newType = Type.copy(self, oldPath, newPath,name);
        newType.fields = {};
        for field in self.fields.itervalues():
            newType.fields[field.name] = field.copy(oldPath,newPath);
        return newType;
    
__init__();
